﻿namespace Aditi.Security
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Xml;
    using System.Xml.Linq;

    using Aditi.Security;

    interface IFilter
    {
        #region Properties

        /// <summary>
        /// Gets or sets a value indicating whether this instance is case sensitive.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is case sensitive; otherwise, <c>false</c>.
        /// </value>
        bool IsCaseSensitive
        {
            get; set;
        }

        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        /// <value>The name.</value>
        string Name
        {
            get; set;
        }

        /// <summary>
        /// Gets or sets the pattern.
        /// </summary>
        /// <value>The pattern.</value>
        string Pattern
        {
            get; set;
        }

        #endregion Properties
    }

    class Configuration
    {
        #region Fields

        public static InputFilter inputFilter;

        const string ConfigFile = @"FilterConfig.xml";
        const string Root = "CONFIGURATION";

        #endregion Fields

        #region Constructors

        /// <summary>
        /// Initializes the <see cref="Configuration"/> class.
        /// </summary>
        static Configuration()
        {
            BuildInputFilterConfiguration();
        }

        #endregion Constructors

        #region Methods

        /// <summary>
        /// Generates Configuration Object from the FilterConfig.xml
        /// </summary>
        private static void BuildInputFilterConfiguration()
        {
            if (!File.Exists(ConfigFile))
                throw new FileNotFoundException("File not found", ConfigFile);
            XDocument xDoc = XDocument.Load(ConfigFile);
            inputFilter = new InputFilter();
            //todo: use XCast logic here with nullable types
            inputFilter.Filters = xDoc.Element(Root).Element(InputFilter.XElement.Root)
                .Elements(InputFilter.XElement.Pattern)
                .Select(x => new Filter(){
                    Name = x.Attribute(InputFilter.XAttrib.Name).Value,
                    Pattern = x.Attribute(InputFilter.XAttrib.Pattern).Value,
                    IsCaseSensitive = XCast<Boolean>(x.Attribute(InputFilter.XAttrib.CaseSensitive)),
                    IsRegex = XCast<Boolean>(x.Attribute(InputFilter.XAttrib.Regex)),
                    Replacement = XCast<string>(x.Attribute(InputFilter.XAttrib.ReplaceStr))
                    }
                    ).ToList();

            inputFilter.Groups = xDoc.Element(Root).Element(InputFilter.XElement.Root)
                .Elements(InputFilter.XElement.Group)
                .ToDictionary(x => x.Attribute(InputFilter.XAttrib.Name).Value,
                x => x.Attribute(InputFilter.XAttrib.Patterns).Value.Split('|')
                    .Select(y =>
                                {
                                    var filter = inputFilter.Filters.Single(z => z.Name==y);
                                    if (filter == null)
                                        throw new ArgumentException("Invalid filter defined in group", y);
                                    return filter;
                                }));
        }

        private static T XCast<T>(XObject container)
            where T : IConvertible
        {
            try
            {
                if (container == null)
                {
                    return default(T);
                }
                TypeConverter tc = TypeDescriptor.GetConverter(typeof (T));
                if (container.NodeType == XmlNodeType.Attribute)
                {
                    return (T) tc.ConvertFrom((container as XAttribute).Value);
                }
                else
                    return (T) tc.ConvertFrom((container as XElement).Value);
            }
            catch (FormatException)
            {
                throw new ArgumentException("Invalid value in configuration file", (container as XAttribute).Value);
            }
            catch (Exception)
            {
                throw new ArgumentException("Invalid value in configuration file", (container as XAttribute).Value);
            }
        }

        #endregion Methods
    }

    class Filter : IFilter
    {
        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="Filter"/> class.
        /// </summary>
        public Filter()
        {
        }

        /// <summary>
        /// Initializes a new instance of the Filter class.
        /// </summary>
        public Filter(string name,string pattern, bool isCaseSensitive, bool isRegex)
        {
            if(string.IsNullOrEmpty(name))
                throw new ArgumentException("Value can not be empty",name);
            if(string.IsNullOrEmpty(pattern))
                throw new ArgumentException("Value can not be empty",pattern);
            this.Name = name;
            this.Pattern = pattern;
            this.IsCaseSensitive = isCaseSensitive;
            this.IsRegex = isRegex;
        }

        #endregion Constructors

        #region Properties

        public bool IsCaseSensitive
        {
            get;set;
        }

        public bool IsRegex
        {
            get; set;
        }

        public string Name
        {
            get;set;
        }

        public string Pattern
        {
            get;set;
        }

        public string Replacement
        {
            get; set;
        }

        #endregion Properties
    }

    class InputFilter
    {
        #region Properties

        public List<Filter> Filters
        {
            get; set;
        }

        public Dictionary<string, IEnumerable<Filter>> Groups
        {
            get; set;
        }

        #endregion Properties

        #region Methods

        public Filter GetFilter(string filterName)
        {
            if (!Filters.Exists(x => x.Name == filterName))
                throw new ArgumentException("Filter with specified name was not found", filterName);
            return this.Filters.Single(x => x.Name == filterName);
        }

        public string GetFilterPattern(string filterName)
        {
            if (!Filters.Exists(x => x.Name == filterName))
                throw new ArgumentException("Filter with specified name was not found", filterName);
            return GetFilter(filterName).Pattern;
        }

        #endregion Methods

        #region Nested Types

        public struct XAttrib
        {
            #region Fields

            public const string CaseSensitive = "casesensitive";
            public const string Name = "name";
            public const string Pattern = "pattern";
            public const string Patterns = "patterns";
            public const string Regex = "regex";
            public const string ReplaceStr = "replacement";

            #endregion Fields
        }

        public struct XElement
        {
            #region Fields

            public const string Group = "GROUP";
            public const string Pattern = "FILTER";
            public const string Root = "INPUTFILTER";

            #endregion Fields
        }

        #endregion Nested Types
    }
}